/*
 * Copyright (c) 2014 pkzd/1.0, 颜文泽(robin)
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     1 Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     2 Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include	<tty.h>
#include	<prf.h>
#include	<sig.h>
#include	<map.h>
#include	<conf.h>
#include	<errno.h>
#include	<string.h>

struct cblock *cbuf;
struct cblock *cfreelist;

int
cinit(void)
{
	struct cblock *cp;
	struct cdevsw *cdp;

	nchrdev = 0;
	cfreelist = NULL;
	if(!(cbuf = malloc0(kernmap, 1))){
		up->p_error = ENOMEM;
		return -1;
	}
	for(cp = cbuf; cp < &cbuf[NCLIST]; cp++){
		cp->c_next = cfreelist;
		cfreelist = cp;
	}
	for(cdp = cdevsw; cdp->d_open; cdp++)	
		nchrdev++;
	return 0;
}

struct cblock *
calloc(void)
{
	struct cblock *cp;

	if(cp = cfreelist)
		cfreelist = cp->c_next;
	return cp;
}

void
cfree(struct cblock *cp)
{
	if(cp){
		cp->c_next = cfreelist;
		cfreelist = cp;
	}
}

int
getc(struct clist *cp)
{
	struct cblock *cbp;

	if(cp->c_cc){
		if(!((uint)cp->c_cf % CBSIZE)){
			cbp = (struct cblock *)((uint)cp->c_cf - CBSIZE);
			cp->c_cf = cbp->c_next->c_info;
			cfree(cbp);
		}
		cp->c_cc--;
		return *cp->c_cf++;
	}
	if(cp->c_cf){
		cbp = (uint)cp->c_cf % CBSIZE ? (struct cblock *)((uint)cp->c_cf & ~(CBSIZE - 1)) : (struct cblock *)((uint)cp->c_cf - CBSIZE);
		cp->c_cf = cp->c_cl = NULL;
		cfree(cbp);
	}
	return EOF;
}

int
putc(struct clist *cp, int c)
{
	struct cblock *cbp;

	cbp = NULL;
	if(!((uint)cp->c_cl % CBSIZE) && !(cbp = calloc())){
		up->p_error = ENOMEM;
		return EOF;
	}
	if(cbp){
		if(cp->c_cl){
			cbp->c_back = (struct cblock *)((uint)cp->c_cl - CBSIZE);
			((struct cblock *)((uint)cp->c_cl - CBSIZE))->c_next = cbp;
		}
		cp->c_cl = cbp->c_info;
		if(!cp->c_cf)
			cp->c_cf = cp->c_cl;
	}
	cp->c_cc++;
	*cp->c_cl++ = c;
	return c;
}

int
unputc(struct clist *cp)
{
	struct cblock *cbp;

	if(!cp->c_cc)
		return EOF;
	if(((uint)cp->c_cl % CBSIZE) == 8){
		cbp = (struct cblock *)((uint)cp->c_cl - 8);
		cp->c_cl = (char *)cbp->c_back + CBSIZE;
		cfree(cbp);
	}
	cp->c_cc--;
	return *--cp->c_cl;
}

void
ttychars(struct tty *tp)
{
	char *cp;
	
	cp = tp->t_cc;
	cp[VBRK] = CBRK;
	cp[VEOF] = CEOT;
	cp[VERASE] = CERASE;
	cp[VKILL] = CKILL;
	cp[VINTR] = CINTR;
	cp[VSTART] = CSTART;
	cp[VSTOP] = CSTOP;
	cp[VCLEAR] = CLEAR;
}

void
flushtty(struct tty *atp)
{
	struct tty *tp;

	tp = atp;
	while(getc(&tp->t_rawq) >= 0)
		;
	while(getc(&tp->t_canq) >= 0)
		;
	while(getc(&tp->t_outq) >= 0)
		;
	tp->t_delct = 0;
}

void
ttyinput(int ac, struct tty *atp)
{
	int c;
	char *cc; 
	struct tty *tp;

	c = ac;
	tp = atp;
loop:
	if(tp->t_rawq.c_cc >= TTYHOG){
		wakeup(&tp->t_rawq);
		goto loop;
	}
	cc = tp->t_cc;
	if(c == cc[VEOF]){
		putc(&tp->t_rawq, CBRK);
		tp->t_delct++; 
	}else if(c == CR || c == CNL){
		putc(&tp->t_rawq, CNL);
		tp->t_delct++;
	}else if(c == cc[VCLEAR]){
		flushtty(tp);
		cnsclear(tp);
		return;
	}else if(c == cc[VERASE]){
		putc(&tp->t_rawq, CERASE);
	}else if(c == cc[VKILL]){
		putc(&tp->t_rawq, CKILL);
	}else if(c == cc[VINTR]){
		signal(tp->t_pgrp, SIGINT);
		return;
	}else if(c == cc[VQUIT]){
		signal(tp->t_pgrp, SIGQUIT);
		return;
	}else
        	putc(&tp->t_rawq, c);
	if(tp->t_flags & ECHO && c != cc[VEOF])
		putchar(c);
	if(tp->t_delct)
		wakeup(&tp->t_rawq);
}

int
canon(struct tty *atp)
{
	int i, c;
	struct tty *tp;

	if(!(tp = atp))
		return 0;
	for(i = 0; (c = getc(&tp->t_rawq)) >= 0; i++){
		switch(c){
		case '\b':
			unputc(&tp->t_canq);
			break;
		case 0x15:
			while((c = unputc(&tp->t_canq)) >= 0 && c != CNL)
				;
			if(c >= 0)
				putc(&tp->t_canq, c);
			break;
		case '\n':
			tp->t_delct--;
			putc(&tp->t_canq, c);
			break;
		case CBRK:
			tp->t_delct--;
			putc(&tp->t_canq, EOF);
			break;
		default:
			putc(&tp->t_canq, c);
			break;
		}
	}
	return i;
}
